﻿// Copyright (c) egmkang wang. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace XServer.Grains
{
    using System;
    using System.Threading.Tasks;
    using Orleans;
    using Orleans.Concurrency;
    using GrainInterfaces;
    using SDK;

    [Reentrant]
    public class UniqueIDGrain : Grain, IUniqueIDGrain
    {
        bool testKey = false;
        long currentSeconds = 0;
        long currentUid = 0;
        long grainNum = -1;

        public async override Task OnActivateAsync()
        {
            this.currentSeconds = Platform.GetSeconds() + 1;
            await base.OnActivateAsync();
        }

        private void TestGrainKeyValid()
        {
            if (!this.testKey)
            {
                var splits = new StringView(this.GetPrimaryKeyString()).Split('_');
                if (splits.Length != 2)
                {
                    throw new ArgumentException(String.Format("Error of UniqueIDGrain's Key:{0}", this.GetPrimaryKeyString()));
                }
                var type = splits[0].ToString();
                this.grainNum = splits[1].ToInt32();
                if (this.grainNum > 1000 || this.grainNum < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(this.grainNum));
                }
                if (String.Format("{0}_{1}", type, this.grainNum) != this.GetPrimaryKeyString())
                {
                    throw new ArgumentException(String.Format("Error of UniqueIDGrain's Key:{0}", this.GetPrimaryKeyString()));
                }

                this.testKey = true;
            }
        }

        //long sec:34;
        //long grainNum:10;
        //long count:20
        private long NewID()
        {
            TestGrainKeyValid();

            long sec = Platform.GetSeconds();
            if (sec != this.currentSeconds)
            {
                long count = this.currentUid & 0xFFFFFL;
                if (count >= 1000000) count = 0;

                this.currentSeconds = sec;
                this.currentUid = this.currentSeconds << 30 | this.grainNum << 20 | count;
            }

            return ++this.currentUid;
        }

        public Task<long> GetBatchNewID(int batch)
        {
            long newUID = this.NewID();
            newUID += batch - 1;
            this.currentUid = newUID;
            return Task.FromResult(newUID);
        }

        public Task<long> GetNewID()
        {
            return Task.FromResult(this.NewID());
        }
    }
}
